﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

using LowLevelGraphics.Histograms;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// 
    /// </summary>
    public enum WHITECOLORBALANCE_MODE
    {
        ONLY_WHITE_COLOR, //only RGB 255, 255, 255 is accepted
        NEAREST_WHITE_COLOR, //sort histogramm by luminance, then get the downwards until we find the first color different from white
        FIND_NEARLY_WHITE_SPOTS, //find a white spot which might correct the color
        JAMESON_HURVICH,            //see the corresponding document
        CUSTOM,  //give this algorithm a color as a basis
        DOMINANT_COLOR, //color count = max for this color
        COLOR_CAST,      //try to remove color casts automatically
        NORMALIZE_AND_REMOVE,
        WHITE_DAYLIGHT,
        WHITE_LIGHTBULB,
        USE_YCBCR_MODE
    }

    /// <summary>
    /// Tries to remove fog from the actual pic
    /// This is also called "dehazing"
    /// </summary>
    public class WhiteColorBalance : BaseImageFilter
    {
        /// <summary>
        /// Specify color balance mode
        /// </summary>
        protected WHITECOLORBALANCE_MODE M_WHITECOLORBALANCE_MODE = WHITECOLORBALANCE_MODE.NEAREST_WHITE_COLOR;

        /// <summary>
        /// brightest color found will be saved here
        /// </summary>
        protected Color m_ColorBrightest = Color.Transparent;

        /// <summary>
        /// Define white point
        /// </summary>
        protected static float[] white = new float[] { 0.78f, 1.48f, 0.74f };	/* White balance daylight */
        
        /// <summary>
        /// define white point of yellow light bulb light
        /// </summary>
        protected static float[] yellow = new float[] { 0.64f, 1.50f, 0.86f };	/* White balance lightbulb */

        /// <summary>
        /// Only for use with ycbcrmode
        /// </summary>
        protected int m_nPhi = 10;

        /// <summary>
        /// Default constructor
        /// </summary>
        public WhiteColorBalance()
            : this(WHITECOLORBALANCE_MODE.USE_YCBCR_MODE)
        {
        }

        /// <summary>
        /// Specify processing mode
        /// White color balance mode is then set to custom at first
        /// </summary>
        /// <param name="_PROCESSINGMODE"></param>
        public WhiteColorBalance(PROCESSING_MODE _PROCESSINGMODE)
        {
            M_PROCESSING_MODE = _PROCESSINGMODE;
            M_WHITECOLORBALANCE_MODE = WHITECOLORBALANCE_MODE.CUSTOM;
        }

        /// <summary>
        /// specify brightest color
        /// White color balance mode is then set to custom at first
        /// </summary>
        /// <param name="_colorBrightest"></param>
        public WhiteColorBalance(Color _colorBrightest)
        {
            m_ColorBrightest = _colorBrightest;
            M_WHITECOLORBALANCE_MODE = WHITECOLORBALANCE_MODE.CUSTOM;
        }

        /// <summary>
        /// Specify white colr balance mode directly
        /// </summary>
        /// <param name="_MODE"></param>
        public WhiteColorBalance(WHITECOLORBALANCE_MODE _MODE)
        {
            M_WHITECOLORBALANCE_MODE = _MODE;
        }

        /// <summary>
        /// used by cloning
        /// </summary>
        /// <param name="_whiteColorBalance"></param>
        internal WhiteColorBalance(WhiteColorBalance _whiteColorBalance)
        {
            m_ColorBrightest = _whiteColorBalance.m_ColorBrightest;
            m_nPhi = _whiteColorBalance.m_nPhi;
            M_WHITECOLORBALANCE_MODE = _whiteColorBalance.M_WHITECOLORBALANCE_MODE;
        }

        /// <summary>
        /// Sets or gets the white color balance mode
        /// </summary>
        public WHITECOLORBALANCE_MODE WHITECOLORBALANCE_MODE
        {
            get { return M_WHITECOLORBALANCE_MODE; }
            set { M_WHITECOLORBALANCE_MODE = value; }
        }

        /// <summary>
        /// Processing mode
        /// </summary>
        public PROCESSING_MODE PROCESSING_MODE
        {
            get { return M_PROCESSING_MODE; }
            set { M_PROCESSING_MODE = value; }
        }

        /// <summary>
        /// Executes this filter
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            Color colorWhite = Definitions.White;
            Statistics statistics = new Statistics(_bitmap);
            statistics.SortBy(PROCESSING_MODE.Luminance);
            StatisticValue histogramValue = statistics[statistics.Count - 1];
            Color colorBrightest = histogramValue.Color;

            UnsafeBitmap bitmap = _bitmap;
            int nWidth = bitmap.Width;
            int nHeight = bitmap.Height;

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.USE_YCBCR_MODE)
            {
                YCbCrMode(bitmap, m_nPhi);
                return _bitmap;
            }

            //Search for the brightest color that's not white
            if (M_WHITECOLORBALANCE_MODE != WHITECOLORBALANCE_MODE.ONLY_WHITE_COLOR)
            {
                bool bFound = false;
                int i = 0;
                int nHistogramCount = statistics.Count;
                for (i = 0; i < nHistogramCount; i++)
                {
                    colorBrightest = statistics[nHistogramCount - i - 1].Color;
                    if (colorBrightest != colorWhite)
                    {
                        bFound = true;
                        break;
                    }
                }
                if (!bFound)
                {
                    return _bitmap;
                }
            }

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.NORMALIZE_AND_REMOVE)
            {
                NormalizeAndRemove(bitmap);
                return _bitmap;
            }

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.COLOR_CAST)
            {
                colorBrightest = Color.FromArgb(statistics.MaxR, statistics.MaxG, statistics.MaxB);
            }            

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.DOMINANT_COLOR)
            {
                statistics.SortBy(PROCESSING_MODE.ColorCount);
                colorBrightest = statistics.ColorList[statistics.ColorList.Count - 1].Key;
            }

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.ONLY_WHITE_COLOR)
            {
                //Highest luminance represents white color? no balance needed then
                if (colorBrightest == colorWhite)
                {
                    return _bitmap;
                }
            }

            //Maybe we might find some pixels almost white
            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.FIND_NEARLY_WHITE_SPOTS)
            {
                List<Point> aPoint = Definitions.EnvironmentPointListClockwise;
                UnsafeBitmap bitmapTemp = (UnsafeBitmap)_bitmap.Clone();
                Color[] aColor = new Color[8];
                List<Color> aColorAverage = new List<Color>();

                List<StatisticValue> aHistogramValue = new List<StatisticValue>();

                for (int y = 0; y < bitmapTemp.Height; y++)
                {
                    for (int x = 0; x < bitmapTemp.Width; x++)
                    {
                        aColor = Segmentation.GetEnvironmentPixelColorArray(bitmapTemp, x, y, aPoint, aColor);
                        aColorAverage.Clear();
                        foreach(Color color in aColor)
                        {
                            if (ColorFunctions.Distance(color, colorWhite)<120)
                            {
                                aColorAverage.Add(color);
                            }
                        }
                        if (aColorAverage.Count > 0)
                        {
                            Color colorAverage = ColorFunctions.AverageColor(aColorAverage.ToArray());
                            aHistogramValue.Add(new StatisticValue(colorAverage));
                        }
                    }
                }
                Statistics histogramm = new Statistics(aHistogramValue);
                statistics.SortBy(PROCESSING_MODE.Luminance);
                colorBrightest = statistics[statistics.Count - 1].Color;

                if (colorBrightest == colorWhite)
                {
                    return _bitmap;
                }
            }

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.CUSTOM)
            {
                if (M_PROCESSING_MODE != PROCESSING_MODE.None)
                {
                    statistics.SortBy(M_PROCESSING_MODE);
                    colorBrightest = statistics[statistics.Count - 1].Color;
                }
                else
                {
                    colorBrightest = m_ColorBrightest;
                }
            }

            //TODO: WhiteColorBalance implement Hirakawa
            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.JAMESON_HURVICH)
            {
                JamesonHurvich(_bitmap);
                return _bitmap;
            }

            if (colorBrightest == Definitions.White)
            {
                return _bitmap;
            }

            //Calculate factor from brightest not white color
            double dRFactor = 255.0f / colorBrightest.R;    //gain value of red
            double dGFactor = 255.0f / colorBrightest.G;    //gain value of green
            double dBFactor = 255.0f / colorBrightest.B;    //gain value of blue

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.WHITE_DAYLIGHT)
            {
                //white daylight factors
                dRFactor = white[0];
                dGFactor = white[1];
                dBFactor = white[2];
            }

            if (M_WHITECOLORBALANCE_MODE == WHITECOLORBALANCE_MODE.WHITE_LIGHTBULB)
            {
                //yellow lightbulb factors
                dRFactor = yellow[0];
                dGFactor = yellow[1];
                dBFactor = yellow[2];
            }

            double nR = -1.0f;
            double nG = -1.0f;
            double nB = -1.0f;
            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    Color color = bitmap.GetPixel(x, y);
                    nR = color.R * dRFactor;
                    nG = color.G * dGFactor;
                    nB = color.B * dBFactor;
                    nR = Math.Round(nR);
                    nG = Math.Round(nG);
                    nB = Math.Round(nB);
                    if (nR > 255) nR = 255;
                    if (nG > 255) nG = 255;
                    if (nB > 255) nB = 255;
                    color = Color.FromArgb((int)nR, (int)nG, (int)nB);
                    bitmap.SetPixel(x, y, color);
                }
            }
            return _bitmap;
        }

        /// <summary>
        /// Normalizes the and remove.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        private static void NormalizeAndRemove(UnsafeBitmap bitmap)
        {
            List<float> aEnergy = new List<float>() { 0.0f, 0.0f, 0.0f };
            List<float> aFactor = new List<float>();

            int nWidth = bitmap.Width;
            int nHeight = bitmap.Height;

            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    Color color = bitmap.GetPixel(x, y);

                    aEnergy[0] += color.R;
                    aEnergy[1] += color.G;
                    aEnergy[2] += color.B;
                }
            }

            for (int k = 0; k < 3; k++)
            {
                aFactor.Add(1.0f / aEnergy[k]);
            }

            float m = aFactor[0] + aFactor[1] + aFactor[2];

            for (int k = 0; k < 3; k++)
            {
                aFactor[k] *= 3.0f / m;
            }

            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    Color color = bitmap.GetPixel(x, y);

                    float nR = color.R * aFactor[0];
                    float nG = color.G * aFactor[1];
                    float nB = color.G * aFactor[2];
                    if (nR > 255) nR = 255;
                    if (nG > 255) nG = 255;
                    if (nB > 255) nB = 255;
                    bitmap.SetPixel(x, y, Color.FromArgb((int)nR, (int)nG, (int)nB));
                }
            }
        }

        /// <summary>
        /// Tests the specified _bitmap.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="phi">The phi.</param>
        private void YCbCrMode(UnsafeBitmap _bitmap, int phi)
        {
            // RGB to YCbCr
            // get mean Y, Cb, Cr

            //TODO: we could use a histogram to get means here...

            double YSum = 0;
            double CbSum = 0;
            double CrSum = 0;
            double n = 0;

            double R, B, G, Y, Cb, Cr;
            double a11, a12, a21, a22, b1, b2, Ar, Ab;

            int nHeight = _bitmap.Height;
            int nWidth = _bitmap.Width;

            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    Color color = _bitmap.GetPixel(x, y);
                    R = color.R;
                    G = color.G;
                    B = color.B;

                    //TODO: we could use YCBCr class from Abstract Colorspace here...
                    Y = 0.299 * R + 0.587 * G + 0.114 * B;
                    Cb = -0.1687 * R - 0.3313 * G + 0.5 * B;
                    Cr = 0.5 * R - 0.4187 * G - 0.0813 * B;

                    if (Y - Math.Abs(Cb) - Math.Abs(Cr) > phi)
                    {
                        YSum += Y;
                        CbSum += Cb;
                        CrSum += Cr;
                        n++;
                    }
                }
            }

            if (n == 0)
            {
                return;
            }

            YSum /= n;
            CbSum /= n;
            CrSum /= n;

            Y = YSum;
            Cb = CbSum;
            Cr = CrSum;


            // ÔöÒæ¼ÆËã

            a11 = -0.1687 * Y - 0.2365 * Cr;
            a12 = 0.5 * Y + 0.866 * Cb;
            a21 = 0.5 * Y + 0.701 * Cr;
            a22 = -0.0813 * Y - 0.1441 * Cb;

            b1 = 0.3313 * Y - 0.114 * Cb - 0.2366 * Cr;
            b2 = 0.4187 * Y - 0.1441 * Cb - 0.299 * Cr;


            Ar = (a22 * b1 - a12 * b2) / (a11 * a22 - a12 * a21);
            Ab = (a21 * b1 - a11 * b2) / (a21 * a12 - a11 * a22);

            for (int y = 0; y < nHeight;y++ )
            {
                for (int x = 0; x < nWidth; x++)
                {
                    Color color = _bitmap.GetPixel(x, y);
                    R = color.R;
                    G = color.G;
                    B = color.B;

                    R *= Ar;
                    if (R > 255)
                    {
                        R = 255;
                    }

                    B *= Ab;
                    if (B > 255)
                    {
                        B = 255;
                    }

                    _bitmap.SetPixel(x, y, Color.FromArgb((int)R, (int)G, (int)B));
                }
            }
        }

        /// <summary>
        /// Hirakawas this instance.
        /// Jameson and Hurvich’s
        /// </summary>
        private UnsafeBitmap JamesonHurvich(UnsafeBitmap _bitmap)
        {
            return _bitmap;
        }

        /// <summary>
        /// Used to create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new WhiteColorBalance(this);
        }
    }
}
